package com.jyniubi.core.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jyniubi.base.exception.Assert;
import com.jyniubi.base.exception.BussinessException;
import com.jyniubi.base.result.ResponseEnum;
import com.jyniubi.core.enums.LendStatusEnum;
import com.jyniubi.core.enums.ReturnMethodEnum;
import com.jyniubi.core.enums.TransTypeEnum;
import com.jyniubi.core.hfb.HfbConst;
import com.jyniubi.core.hfb.RequestHelper;
import com.jyniubi.core.mapper.LendMapper;
import com.jyniubi.core.pojo.entity.*;
import com.jyniubi.core.pojo.vo.BorrowInfoApprovalVO;
import com.jyniubi.core.pojo.vo.BorrowerDetailVO;
import com.jyniubi.core.service.*;
import com.jyniubi.core.utils.*;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.DateTime;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 * 标的准备表 服务实现类
 * </p>
 *
 * @author jyzhang
 * @since 2024-04-27
 */
@Service
@Slf4j
public class LendServiceImpl extends ServiceImpl<LendMapper, Lend> implements LendService {

    @Resource
    private DictService dictService;
    @Resource
    private BorrowerService borrowerService;
    @Resource
    private UserAccountService userAccountService;
    @Resource
    private UserInfoService userInfoService;
    @Resource
    private TransFlowService transFlowService;
    @Resource
    private LendItemService lendItemService;
    @Resource
    private LendService lendService;
    @Resource
    private LendReturnService lendReturnService;
    @Resource
    private LendItemReturnService lendItemReturnService;

    @Override
    public void createLend(BorrowInfo borrowInfo, BorrowInfoApprovalVO borrowInfoApprovalVO) {
        Lend lend = new Lend();
        lend.setUserId(borrowInfo.getUserId());
        lend.setBorrowInfoId(borrowInfo.getId());
        //额外的标的的唯一编号
        lend.setLendNo(LendNoUtils.getLendNo());
        lend.setTitle(borrowInfoApprovalVO.getTitle());
        lend.setAmount(borrowInfo.getAmount());
        lend.setPeriod(borrowInfo.getPeriod());
        lend.setLendYearRate(borrowInfoApprovalVO.getLendYearRate()
                .divide(new BigDecimal("100"),8,BigDecimal.ROUND_DOWN));
        lend.setServiceRate(borrowInfoApprovalVO.getServiceRate()
                .divide(new BigDecimal("100"),8,BigDecimal.ROUND_DOWN));
        lend.setReturnMethod(borrowInfo.getReturnMethod());
        lend.setLowestAmount(new BigDecimal(100));
        lend.setInvestNum(0);
        lend.setLendStartDate(borrowInfoApprovalVO.getLendStartDate());
        //lend.setLendEndDate(borrowInfoApprovalVO.getLendStartDate()+borrowInfo.getPeriod());
        //borrowInfoApprovalVO.getLendStartDate()+borrowInfo.getPeriod()
        //标的结束时间： 起息日+投资期数
        lend.setLendEndDate(new DateTime(borrowInfoApprovalVO.getLendStartDate())
                .plusMonths(borrowInfo.getPeriod())
                .toDate());
        lend.setLendInfo(borrowInfoApprovalVO.getLendInfo());
        //预期收益： 投资期数*月化率*标的金额
        //月化率= 年化率/12
        BigDecimal lendMonthRate = borrowInfoApprovalVO.getLendYearRate().divide(new BigDecimal(1200),
                8, BigDecimal.ROUND_DOWN);
        BigDecimal expectAmount = lendMonthRate.multiply(borrowInfo.getAmount())
                .multiply(new BigDecimal(borrowInfo.getPeriod()));
        lend.setExpectAmount(expectAmount);
        lend.setInvestAmount(BigDecimal.ZERO);
        //初始化标的时 状态设置为募资中
        lend.setStatus(LendStatusEnum.INVEST_RUN.getStatus());
        lend.setPublishDate(new Date());
        this.save(lend);
    }

    @Override
    public List<Lend> getLendList() {
        List<Lend> lendList = this.list();
        lendList.forEach(lend -> {
            //这种方法也可以获取状态，不用下面枚举类
            //String msgByStatus = LendStatusEnum.getMsgByStatus(lend.getStatus());
            updateLendDetail(lend);
        });
        return lendList;
    }

    private void updateLendDetail(Lend lend) {
        String status;
        switch (lend.getStatus()){
            case 0:
                status = LendStatusEnum.CHECK.getMsg();
                break;
            case 1:
                status = LendStatusEnum.INVEST_RUN.getMsg();
                break;
            case 2:
                status = LendStatusEnum.PAY_RUN.getMsg();
                break;
            case 3:
                status = LendStatusEnum.PAY_OK.getMsg();
                break;
            case 4:
                status = LendStatusEnum.FINISH.getMsg();
                break;
            case -1:
                status = LendStatusEnum.CANCEL.getMsg();
                break;
            default:
                throw new BussinessException(ResponseEnum.LEND_STATUS_ERROR);
        }
        lend.getParam().put("status",status);
        Dict dict = dictService.getDictByDictCodeAndValue("returnMethod", lend.getReturnMethod());
        lend.getParam().put("returnMethod",dict.getName());
    }

    @Override
    public Map<String, Object> getLendDetail(String id) {
        Lend lend = this.getById(id);
        updateLendDetail(lend);
        Borrower borrower = borrowerService.getOne(Wrappers.lambdaQuery(Borrower.class).eq(Borrower::getUserId, lend.getUserId()).select(Borrower::getId));
        BorrowerDetailVO borrowerDetailVO = borrowerService.getBorrowerDetailById(borrower.getId().toString());
        Map<String,Object> map = new HashMap<>();
        map.put("lend",lend);
        map.put("borrowerDetailVO",borrowerDetailVO);
        return map;
    }

    @Override
    public BigDecimal getInterestCount(BigDecimal investAmount, String id) {
        Lend lend = this.getById(id);
        Integer returnMethod = lend.getReturnMethod();
        BigDecimal lendYearRate = lend.getLendYearRate();
        Integer period = lend.getPeriod();
        BigDecimal interestCount;
        if (returnMethod.equals(ReturnMethodEnum.ONE.getMethod())){
            interestCount = Amount1Helper.getInterestCount(investAmount, lendYearRate, period);
        }else if (returnMethod.equals(ReturnMethodEnum.TWO.getMethod())){
            interestCount = Amount2Helper.getInterestCount(investAmount, lendYearRate, period);
        }else if (returnMethod.equals(ReturnMethodEnum.THREE.getMethod())){
            interestCount = Amount3Helper.getInterestCount(investAmount, lendYearRate, period);
        }else{
            interestCount = Amount4Helper.getInterestCount(investAmount, lendYearRate, period);
        }
        return interestCount;
    }

    @Override
    public void makeLoan(String lendId) {
        //标的状态为募资中
        Lend lend = this.getById(lendId);
        if (lend == null){
            throw new BussinessException(ResponseEnum.LEND_NULL_ERROR);
        }
        Assert.assertTrue(!lend.getStatus().equals(LendStatusEnum.INVEST_RUN.getStatus()),ResponseEnum.LEND_RETURN_ERROR);
        //组装请求
        HashMap<String, Object> map = new HashMap<>();
        map.put("agentId", HfbConst.AGENT_ID);
        map.put("agentProjectCode",lend.getLendNo());
        map.put("agentBillNo",LendNoUtils.getChargeNo());
        //商户手续费
        //月年化
        BigDecimal monthRate = lend.getServiceRate().divide(new BigDecimal(12), 8, BigDecimal.ROUND_DOWN);
        //募资总金额*月化率*期数
        BigDecimal mchFee = lend.getInvestAmount().multiply(new BigDecimal(lend.getPeriod()))
                .multiply(monthRate);
        map.put("mchFee",mchFee);
        map.put("timestamp", RequestHelper.getTimestamp());
        map.put("sign",RequestHelper.getSign(map));
        //请求响应结果
        JSONObject jsonObject = RequestHelper.sendRequest(map, HfbConst.MAKE_LOAD_URL);
        log.info("放款结果：" + jsonObject.toJSONString());
        //判断响应结果code
        Assert.assertTrue(!"0000".equals(jsonObject.getString("resultCode")),ResponseEnum.LEND_RETURN_STATUS_ERROR);
        //本地借款人账户入账
        BigDecimal borrowAmt = lend.getInvestAmount().subtract(mchFee);
        Long userId = lend.getUserId();
        UserAccount userAccount = userAccountService.getOne(Wrappers.lambdaQuery(UserAccount.class).
                eq(UserAccount::getUserId, userId));
        userAccount.setAmount(userAccount.getAmount().add(borrowAmt));
        userAccountService.updateById(userAccount);
        //保存交易日志
        UserInfo userInfo = userInfoService.getById(userId);
        TransFlow transFlow = new TransFlow();
        transFlow.setUserId(userId);
        transFlow.setUserName(userInfo.getName());
        transFlow.setTransNo(LendNoUtils.getTransNo());
        transFlow.setTransType(TransTypeEnum.BORROW_BACK.getTransType());
        transFlow.setTransTypeName(TransTypeEnum.BORROW_BACK.getTransTypeName());
        transFlow.setTransAmount(userAccount.getAmount());
        transFlow.setMemo(TransTypeEnum.BORROW_BACK.getTransTypeName()+": "+borrowAmt+" 元");
        transFlowService.save(transFlow);
        //遍历lend_item,生成投资记录
        List<LendItem> lendItemList = lendItemService.list(Wrappers.lambdaQuery(LendItem.class).
                eq(LendItem::getLendId, lendId));
        lendItemList.forEach(lendItem -> {
            BigDecimal investAmount = lendItem.getInvestAmount();
            Long investUserId = lendItem.getInvestUserId();
            UserAccount investAccount = userAccountService.getOne(Wrappers.lambdaQuery(UserAccount.class).
                    eq(UserAccount::getUserId, investUserId));
            investAccount.setFreezeAmount(investAccount.getFreezeAmount().subtract(investAmount));
            userAccountService.updateById(investAccount);
            TransFlow investTransFlow = new TransFlow();
            investTransFlow.setUserId(investUserId);
            investTransFlow.setUserName(lendItem.getInvestName());
            investTransFlow.setTransNo(LendNoUtils.getTransNo());
            investTransFlow.setTransType(TransTypeEnum.INVEST_UNLOCK.getTransType());
            investTransFlow.setTransTypeName(TransTypeEnum.INVEST_UNLOCK.getTransTypeName());
            investTransFlow.setTransAmount(investAccount.getAmount());//充值金额 = 用户提交的充值金额+商户手续费
            investTransFlow.setMemo(TransTypeEnum.INVEST_UNLOCK.getTransTypeName()+": "+investAmount+" 元");
            transFlowService.save(investTransFlow);
        });
        //3.修改标的状态为还款
        lend.setStatus(LendStatusEnum.PAY_RUN.getStatus());
        lendService.updateById(lend);
        //4.生成还款计划：lend_return
        List<LendReturn> lendReturns = new ArrayList<>();
        for (Integer i = 1; i <= lend.getPeriod(); i++){
            LendReturn lendReturn = new LendReturn();
            lendReturn.setLendId(lend.getId());
            lendReturn.setBorrowInfoId(lend.getBorrowInfoId());
            lendReturn.setReturnNo(LendNoUtils.getReturnNo());
            lendReturn.setUserId(lend.getUserId());
            lendReturn.setAmount(lend.getAmount());
            lendReturn.setBaseAmount(lend.getInvestAmount());
            lendReturn.setCurrentPeriod(i);
            lendReturn.setLendYearRate(lend.getLendYearRate());
            lendReturn.setReturnMethod(lend.getReturnMethod());
            //投资人较多： 每个投资人会生成 一个 还款计划对应的一个回款计划
            //生成回款计划后：累加每一期所有投资人回款计划的以下三个值 设置给还款计划
            //            lendReturn.setPrincipal();//本期还款本金
            //            lendReturn.setInterest();//本期还款的利息
            //            lendReturn.setTotal();//本期还款总金额
            lendReturn.setFee(BigDecimal.ZERO);
            if (i==1){
                lendReturn.setReturnDate(lend.getLendStartDate());
            }else {
                lendReturn.setReturnDate(new DateTime(lend.getLendStartDate()).plusMonths(i-1).toDate());
            }
            lendReturn.setOverdue(false);
            lendReturn.setLast(i.equals(lend.getPeriod()));
            lendReturn.setStatus(0);//未归还
            lendReturns.add(lendReturn);
        }
        //批量保存还款计划
        lendReturnService.saveBatch(lendReturns);
        //因为后续要使用 期数 获取lendReturn对象的id，可以将lendReturns集合中的每个 期数id的映射转为map
        Map<Integer, Long> periodReturnIdMap = lendReturns.stream().collect(Collectors.toMap(LendReturn::getCurrentPeriod, LendReturn::getId));
        //5.生成回款计划：lend_item_return
        //每个投资记录生成标的得期数个回款计划
        //遍历投资记录
        List<LendItemReturn> lendItemReturns = new ArrayList<>();
        lendItemList.forEach(lendItem -> {
            Map<Integer, BigDecimal> perMonthPrincipal;
            Map<Integer, BigDecimal> perMonthInterest;
            if (ReturnMethodEnum.ONE.getMethod().equals(lend.getReturnMethod())){
                perMonthPrincipal = Amount1Helper.getPerMonthPrincipal(lendItem.getInvestAmount(), lendItem.getLendYearRate(), lend.getPeriod());
                perMonthInterest = Amount1Helper.getPerMonthInterest(lendItem.getInvestAmount(), lendItem.getLendYearRate(), lend.getPeriod());
            } else if (ReturnMethodEnum.TWO.getMethod().equals(lend.getReturnMethod())){
                perMonthPrincipal = Amount2Helper.getPerMonthPrincipal(lendItem.getInvestAmount(), lendItem.getLendYearRate(), lend.getPeriod());
                perMonthInterest = Amount2Helper.getPerMonthInterest(lendItem.getInvestAmount(), lendItem.getLendYearRate(), lend.getPeriod());
            } else if (ReturnMethodEnum.THREE.getMethod().equals(lend.getReturnMethod())){
                perMonthPrincipal = Amount3Helper.getPerMonthPrincipal(lendItem.getInvestAmount(), lendItem.getLendYearRate(), lend.getPeriod());
                perMonthInterest = Amount3Helper.getPerMonthInterest(lendItem.getInvestAmount(), lendItem.getLendYearRate(), lend.getPeriod());
            } else {
                perMonthPrincipal = Amount4Helper.getPerMonthPrincipal(lendItem.getInvestAmount(), lendItem.getLendYearRate(), lend.getPeriod());
                perMonthInterest = Amount4Helper.getPerMonthInterest(lendItem.getInvestAmount(), lendItem.getLendYearRate(), lend.getPeriod());
            }
            for (Integer i = 1;i<=lend.getPeriod();i++){
                LendItemReturn lendItemReturn = new LendItemReturn();
                lendItemReturn.setLendReturnId(periodReturnIdMap.get(i));
                lendItemReturn.setLendItemId(lendItem.getId());
                lendItemReturn.setLendId(lendItem.getLendId());
                lendItemReturn.setInvestUserId(lendItem.getInvestUserId());
                lendItemReturn.setInvestAmount(lendItem.getInvestAmount());
                lendItemReturn.setCurrentPeriod(i);
                lendItemReturn.setLendYearRate(lendItem.getLendYearRate());
                lendItemReturn.setReturnMethod(lend.getReturnMethod());
                if (ReturnMethodEnum.FOUR.getMethod().equals(lend.getReturnMethod())){
                    lendItemReturn.setPrincipal(perMonthPrincipal.get(1));
                    lendItemReturn.setInterest(perMonthInterest.get(1));
                    lendItemReturn.setTotal(lendItemReturn.getPrincipal().add(lendItemReturn.getInterest()));
                } else {
                    lendItemReturn.setPrincipal(perMonthPrincipal.get(i));
                    lendItemReturn.setInterest(perMonthInterest.get(i));
                    lendItemReturn.setTotal(lendItemReturn.getPrincipal().add(lendItemReturn.getInterest()));
                }
                lendItemReturn.setFee(BigDecimal.ZERO);
                lendItemReturn.setReturnDate(new DateTime(lend.getLendStartDate()).plusMonths(i-1).toDate());
                lendItemReturn.setOverdue(false);
                lendItemReturn.setOverdueTotal(BigDecimal.ZERO);
                lendItemReturn.setStatus(0);
                lendItemReturns.add(lendItemReturn);
            }
        });
        lendItemReturnService.saveBatch(lendItemReturns);
        //计算每个还款计划的 本金利息 总金额
        lendReturns.forEach(lendReturn -> {
            //通过正在遍历的还款计划对象的 期数 获取 回款计划集合中相同期数的 回款计划集合
            Optional<BigDecimal> totalPrincipalOptional = lendItemReturns.stream()
                    //挑选指定期的  回款计划集合
                    .filter(lendItemReturn -> lendItemReturn.getCurrentPeriod().equals(lendReturn.getCurrentPeriod()))
                    // 将挑选的 所有的回款计划对象的 本金字段挑选出来
                    .map(lendItemReturn -> Optional.ofNullable(lendItemReturn.getPrincipal()).orElse(BigDecimal.ZERO))
                    //遍历上一步挑选的本金字段数据集合  每两个相加
                    .reduce(BigDecimal::add);
            //防止流中没有元素或所有principal都是null），这将抛出一个NoSuchElementException
            BigDecimal totalPrincipal = totalPrincipalOptional.orElse(BigDecimal.ZERO);

            Optional<BigDecimal> totalInterestOptional = lendItemReturns.stream()
                    //挑选指定期的  回款计划集合
                    .filter(lendItemReturn -> lendItemReturn.getCurrentPeriod().equals(lendReturn.getCurrentPeriod()))
                    // 将挑选的 所有的回款计划对象的 本金字段挑选出来
                    .map(lendItemReturn -> Optional.ofNullable(lendItemReturn.getPrincipal()).orElse(BigDecimal.ZERO))
                    //遍历上一步挑选的本金字段数据集合  每两个相加
                    .reduce(BigDecimal::add);
            BigDecimal totalInterest = totalInterestOptional.orElse(BigDecimal.ZERO);

            Optional<BigDecimal> totalSumOptional = lendItemReturns.stream()
                    //挑选指定期的  回款计划集合
                    .filter(lendItemReturn -> lendItemReturn.getCurrentPeriod().equals(lendReturn.getCurrentPeriod()))
                    // 将挑选的 所有的回款计划对象的 本金字段挑选出来
                    .map(lendItemReturn -> Optional.ofNullable(lendItemReturn.getPrincipal()).orElse(BigDecimal.ZERO))
                    //遍历上一步挑选的本金字段数据集合  每两个相加
                    .reduce(BigDecimal::add);
            BigDecimal totalSum = totalSumOptional.orElse(BigDecimal.ZERO);

            lendReturn.setPrincipal(totalPrincipal);
            lendReturn.setInterest(totalInterest);
            lendReturn.setTotal(totalSum);
        });
        lendReturnService.updateBatchById(lendReturns);
    }
}
